/*
 * @Description: axios二次封装
 * @Version: 2.0
 * @Autor: lhl
 * @Date: 2020-11-22 16:15:13
 * @LastEditors: lhl
 * @LastEditTime: 2021-08-22 21:23:55
 */
import axios from "axios";
import { Toast } from "vant";

const instance = axios.create({
  baseURL: process.env.VUE_APP_BASE_URL, // 环境变量里面读取对应的 host 这里可以判断下dev走 mock数据 生产测试走接口数据
  timeout: 10000, // `timeout` 指定请求超时的毫秒数(0 表示无超时时间) 如果请求话费了超过 `timeout` 的时间，请求将被中断
  withCredentials: true, // 表示跨域请求时是否需要使用凭证
});

// 全局配置
// instance.defaults.headers.common['Authorization'] = AUTH_TOKEN;
instance.defaults.headers.post["Content-Type"] =
  "application/x-www-form-urlencoded";

// 取消重复请求
const repeatRequstList = [];
// 使用 cancel token 取消请求
const CancelToken = axios.CancelToken;

// 定义一个缓存池用来缓存数据
let cache = {};
// 过期时间
const EXPIRE_TIME = 60000;

// 移除重复请求
const removePending = (config) => {
  for (const key in repeatRequstList) {
    console.log(key, "key");
    const item = +key;
    const list = repeatRequstList[key];
    // 当前请求在数组中存在时执行函数体
    if (
      list.url === config.url &&
      list.method === config.method &&
      JSON.stringify(list.params) === JSON.stringify(config.params) &&
      JSON.stringify(list.data) === JSON.stringify(config.data)
    ) {
      // 执行取消操作
      list.cancel("操作太频繁，请稍后再试");
      // 从数组中移除记录
      repeatRequstList.splice(item, 1);
    }
  }
};

// 添加请求拦截器
instance.interceptors.request.use(
  (config) => {
    // 在发送请求之前做些什么
    // 不传递默认开启loading
    if (!config.hideloading) {
      // 默认开启 loading 效果不需要的 API层 单独配置  hideloading: true
      Toast.loading({
        forbidClick: true,
      });
    }
    // 移除重复请求
    removePending(config);
    config.cancelToken = new CancelToken((c) => {
      repeatRequstList.push({
        url: config.url,
        method: config.method,
        params: config.params,
        data: config.data,
        cancel: c,
      });
    });
    // 缓存请求
    // 如果需要缓存--考虑到并不是所有接口都需要缓存的情况
    if (config.cache) {
      let source = CancelToken.source();
      config.cancelToken = source.token;
      // 去缓存池获取缓存数据
      let data = cache[config.url];
      // 获取当前时间戳
      let expire_time = getExpireTime();
      // 判断缓存池中是否存在已有数据 存在的话 再判断是否过期
      // 未过期 source.cancel会取消当前的请求 并将内容返回到拦截器的err中
      if (data && expire_time - data.expire < EXPIRE_TIME) {
        source.cancel(data);
      }
    }

    return config;
  },
  (error) => {
    // 对请求错误做些什么
    return Promise.reject(error);
  }
);

// 添加响应拦截器
instance.interceptors.response.use(
  (response) => {
    // 对响应数据做点什么
    Toast.clear();
    // 移除重复请求
    removePending(response.config);
    // const {data} = response // 去掉那一层data

    // 只缓存get请求
    if (response.config.method === "get" && response.config.cache) {
      // 缓存数据 并将当前时间存入 方便之后判断是否过期
      let data = {
        expire: getExpireTime(),
        data: response.data,
      };
      cache[`${response.config.url}`] = data;
    }
    return response;
  },
  (error) => {
    // 对响应错误做点什么  断网 无权限 token 过期的 错误 code 做对应的处理即可
    // 请求拦截器中的source.cancel会将内容发送到 error中
    // 通过axios.isCancel(error)来判断是否返回有数据 有的话直接返回给用户
    if (axios.isCancel(error)) return Promise.resolve(error.message.data);
    // 如果没有的话 则是正常的接口错误 直接返回错误信息给用户

    // 超时重新请求
    const config = error.config;
    // 全局的请求次数,请求的间隙
    const [RETRY_COUNT, RETRY_DELAY] = [3, 1000];

    if (config && RETRY_COUNT) {
      // 设置用于跟踪重试计数的变量
      config.__retryCount = config.__retryCount || 0;
      // 检查是否已经把重试的总数用完
      if (config.__retryCount >= RETRY_COUNT) {
        return Promise.reject(response || { message: error.message });
      }
      // 增加重试计数
      config.__retryCount++;

      // 创造新的Promise来处理指数后退
      // 在typescript中定义promise返回类型 首先要在tsconfig.json中配置ES2015.promise的lib 不然ts无法支持promise
      const backClose = new Promise((resolve) => {
        setTimeout(() => {
          // 写上 null or undefined 不然ts下面会报错
          resolve(null);
        }, RETRY_DELAY || 1);
      });

      // instance重试请求的Promise
      return backClose.then(() => {
        return instance(config);
      });
    }
    return Promise.reject(error);
  }
);

// 获取当前时间
function getExpireTime() {
  return new Date().getTime();
}

export default instance;
